#ifndef __STRATEGY_HPP__
#define __STRATEGY_HPP__

#include "../utility/ojData.h"
#include "../utility/os.h"
#include "../utility/fileio.h"
#include "../utility/md5.h"
#include "../languages/baseLang.h"
#include <ostream>
#include <string>
using std::operator""s;
using std::endl;

#ifdef  ENABLE_DB_JUDGE
#include <mysql/mysql.h>
#include "../judge_types/database_judge.h"
#endif

namespace general_execution_strategy {

    template <class Fun, class CheckFun = std::function<bool()>>
    ProcessStatus check_and_update(
                    const std::string& src, 
                    const std::string& dst, 
                    Fun&& fun, 
                    const std::string& check_file = "",
#ifdef MULTI_PROCESS
                    const std::string& lock_file = "",
#endif
                    CheckFun&& check_fun = []{return false;}
                ) {
        Log::instance().getOutput() << "check and update:" << endl
            << "src:" << src << endl
            << "dst:" << dst << endl;
        string dst_check;
        if (check_file.empty()) {
            dst_check = dst + ".check";
        }
#ifdef MULTI_PROCESS
        string dst_lock;
        if (lock_file.empty()) {
            dst_lock = dst + ".lock";
        }
#endif

        ProcessStatus ps;
        if (!existsFile(dst) || 
            !existsFile(dst_check) || 
            getLastUpdateTime(src) > getLastUpdateTime(dst_check) || 
            calc_md5(fileToStr(src)) != fileToStr(dst_check) ||
            check_fun()) {
#ifdef MULTI_PROCESS
            createFileInMultiDir(dst_lock);
            FileLock file_lock(LOCK_EX, dst_lock);
            if (!existsFile(dst) || 
                !existsFile(dst_check) || 
                getLastUpdateTime(src) > getLastUpdateTime(dst_check) || 
                calc_md5(fileToStr(src)) != fileToStr(dst_check) ||
                check_fun()) {
#endif
                ps = std::forward<Fun>(fun)();
                if (ps.result != RUN_OK) {
                    return ps;
                }
                if (!std::filesystem::exists(dst_check)) {
                    createFileInMultiDir(dst_check);
                }
                {
                    std::ofstream osf{dst_check};
                    osf << calc_md5(fileToStr(src));
                }
#ifdef MULTI_PROCESS
            } else {
                ps.result = RUN_OK;
                ps.output = "dstFile is Latest Version:" + dst;
            }
#endif
        } else {
            ps.result = RUN_OK;
            ps.output = "dstFile is Latest Version:" + dst;
        }
        return ps;
    }

    static inline auto ok_fun() {
        return ProcessStatus{.result = RUN_OK}; 
    };
    // ForceUpdate = ture means force copy file and execute compileStr
    // ForceUpdate = false means check `targetCodeFile`.out file determine whether to copy and compile
    template <bool ForceUpdate = true, class UpdateFun = decltype(ok_fun)>
    ProcessStatus _copy(const std::string& sourceCodeFile, 
                        const std::string& targetCodeFile, 
                        const Language* lang, 
                        UpdateFun&& updateFun = ok_fun) {
        Log::instance().getOutput() << "entry _copy" << std::endl;
        defer(
            Log::instance().getOutput() << "exit _copy" << std::endl;
        );
        auto copy_impl = [&]() {
            ProcessStatus ps {.result = RUN_OK};
            std::string res;
            Log::instance().getOutput() << "_copy - copy_impl, lang addr:" << lang << std::endl;
            res = lang->prepare(sourceCodeFile, targetCodeFile);
            Log::instance().getOutput() << "lang prepare ok" << std::endl;
            if (res.empty()) {
                return ProcessStatus{
                            .result = SYS_ERROR,
                            .error = "copy '"s + sourceCodeFile + "' to '"s + targetCodeFile + "' error "s,
                };
            }
            ps = updateFun();
            Log::instance().getOutput() << "update fun result:" << codeToStr[ps.result] << endl;
            return ps;
        };

        if constexpr (ForceUpdate) {
            return copy_impl();
        }

        return check_and_update(sourceCodeFile, 
                                targetCodeFile, 
                                copy_impl
                                );
    }

    // ForceUpdate = ture means force copy file and execute compileStr
    // ForceUpdate = false means check `targetCodeFile`.out file determine whether to copy and compile
    template <bool ForceUpdate = true, class UpdateFun = decltype(ok_fun)>
    ProcessStatus _compile(const std::string& CodeFile, 
                        const std::string& BinFile, 
                        const std::string& compileStr,
                        UpdateFun&& updateFun = ok_fun) {

        auto compile_impl = [&]() {
            ProcessStatus ps {.result = RUN_OK};
            if (!compileStr.empty()) {
                ps = commandExecuteResult(compileStr, COMPILE_TIME_LIMIT, 0, NON_SYSCHECK | NON_NOBODY);
            }
            if (ps.result != RUN_OK) {
                return ps;
            }
            ps = updateFun();
            return ps;
        };

        if constexpr (ForceUpdate) {
            return compile_impl();
        }

        return check_and_update(CodeFile, 
                                BinFile, 
                                compile_impl
                                );
    }

    // ForceUpdate = ture means force execute runStr
    // ForceUpdate = false means check `outFile`.out file determine whether to run code 
    template <bool ForceUpdate, class JudgeType>
    ProcessStatus run_code(const std::string& runStr, int timeLimit, int memoryLimit, const std::string& inFile, const std::string& outFile, Language* lang, const string& workPath) {
        if (inFile.empty()) {
            return ProcessStatus{ 
                            .result = LESS_STDFILES,
                            .error = "user code run file unknown:" + inFile,
                        };
        }
        Log::instance().getOutput() << "inFile:" << inFile << endl;
        Log::instance().getOutput() << "outFile:" << outFile << endl;

        auto run_code_impl = [&]() {
            if constexpr (std::is_same_v<JudgeType, void>) {
                return commandExecuteResult(runStr, 
                                            timeLimit, memoryLimit, 0, 
                                            ProcessSTD(inFile, outFile, ""), 
                                            workPath,
                                            lang->disableSysCallSet);
            } else {
                return commandExecuteResult(runStr, 
                                            timeLimit, memoryLimit, 0, 
                                            ProcessSTD(inFile, outFile, ""), 
                                            workPath,
                                            lang->disableSysCallSet + JudgeType::disableSysCallSet);
            }
        };

        ProcessStatus ps;
        if constexpr (ForceUpdate) {
            return run_code_impl();
        }

        return check_and_update(inFile, 
                                outFile, 
                                run_code_impl
                                );

    }

    ProcessStatus twins_process_run_impl(
                        const Language* lang1, const string& command1, 
                        const Language* lang2, const string& command2, 
                        int timeLimit, int memoryLimit, 
                        const string& inFile, const string& outFile, 
                        const string& path1 = "", const string& path2 = "");

    template <bool ForceUpdata>
    ProcessStatus twins_process_run(
                        const Language* lang1, const string& command1, 
                        const Language* lang2, const string& command2, 
                        int timeLimit, int memoryLimit, 
                        const string& inFile, const string& outFile, 
                        const string& path1 = "", const string& path2 = ""){
        ProcessStatus ps;

        auto local_twins_process_run_impl = [&]() {
            return twins_process_run_impl(lang1, command1, lang2, command2, timeLimit, memoryLimit, inFile, outFile, path1, path2);
        };

        if constexpr (ForceUpdata) {
            return local_twins_process_run_impl();
        }

        return check_and_update(inFile, 
                                outFile, 
                                local_twins_process_run_impl
                                );
    }

#ifdef ENABLE_DB_JUDGE
    ProcessStatus database_update(MYSQL *conn, const string& database, const string& createSql, const string& spjCachePath);

    template <bool ForceUpdate>
    ProcessStatus run_sql(MYSQL *conn, const string& database, const string& sqlFile, int mode, const string& inFile, const string& outFile, int timeLimit, int memoryLimit) {
        defer(
            mysql_rollback(conn);
            mysql_refresh(conn, REFRESH_TABLES);
        );

        auto run_sql_impl = [&]() {
            ProcessStatus local_ps;
            int err;

            err = mysql_change_user(conn, databaseParams.user.c_str(), databaseParams.user_passwd.c_str(), database.c_str());
            if (err) {
                    Log::instance().getError() << "database change user occur error" << std::endl;
                    local_ps.result = MYSQL_ERROR;
                    local_ps.error = "create database error:"s + mysql_error(conn);
                    return local_ps;
            }
            mysql_autocommit(conn, false);
            mysql_set_character_set(conn, "utf8");

            local_ps = write_example(conn, database, fileToStr(inFile));
            if (local_ps.result != RUN_OK) {
                return local_ps;
            }

            if (mode == DATABASE_SELECT_JUDGE) {
                local_ps = run_single_select_sql(conn, database, fileToStr(sqlFile), timeLimit);
            } else if (mode == DATABASE_MODIFY_JUDGE) {
                local_ps = run_single_modify_sql(conn, database, fileToStr(sqlFile), timeLimit);
            }
            mysql_rollback(conn);
            mysql_refresh(conn, REFRESH_TABLES);


            err = mysql_change_user(conn, databaseParams.root.c_str(), databaseParams.passwd.c_str(), database.c_str());
            if (err) {
                    Log::instance().getError() << "database change user occur error" << std::endl;
                    local_ps.result = MYSQL_ERROR;
                    local_ps.error = "create database error:"s + mysql_error(conn);
                    return local_ps;
            }
            mysql_autocommit(conn, false);
            mysql_set_character_set(conn, "utf8");


            if (local_ps.result != RUN_OK) {
                return local_ps;
            }

            // select查询去掉表头
            if (mode == DATABASE_SELECT_JUDGE) {
                JsonArray &arr = local_ps.extern_info->asArray();
                arr.erase(arr.begin());
            }

            if (outFile != "" && !strToFile(outFile, local_ps.extern_info->json())) {
                local_ps.result = RE;
                local_ps.error = "DATABASE_JUDGE result write spj_out error";
                return local_ps;
            }

            return local_ps;
        };

        if constexpr (ForceUpdate) {
            return run_sql_impl();
        }

        Log::instance().getOutput() << "inFile[" << inFile << "],outFile[" << outFile << "],sqlFile[" << sqlFile << "]" << endl;
        return check_and_update(inFile, 
                                outFile, 
                                run_sql_impl
                                );
    }

#endif
};

#endif /* __STRATEGY_HPP__ */
